package hooks

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"

	log "atomgit.com/atomgit_dev/alog/alogcore"
	"atomgit.com/atomgit_dev/git-hooks/pkg/config"
	"atomgit.com/atomgit_dev/git-hooks/pkg/internal/agitext"
	"atomgit.com/atomgit_dev/git-hooks/pkg/streamio"
	pb "atomgit.com/atomgit_dev/satellite-proto/go"
)

const (
	_postFetchAction = "GIT_UPLOAD_PACK_PROXY"
)

// This constant to control the behavior of post-fetch hook, and ensure
// compatibility with the old logic
const (
	_sync  = "0"
	_proxy = "1"
)

// StartPostFetch calls satellite.PostFetchHook to execute post-fetch hook.
func StartPostFetch(ctx context.Context, args []string) (int, error) {
	switch getPostFetchHookEnv() {
	case pb.PostFetchHookRequest_SYNC:
		return fetchSync(ctx)
	case pb.PostFetchHookRequest_PROXY:
		return fetchProxy(ctx, args)
	default:
		return fetchSync(ctx)
	}
}

func fetchSync(ctx context.Context) (int, error) {
	// 1.Parse env.
	params, err := agitext.GetSpaceXParamsFromEnv()
	if err != nil {
		return 1, fmt.Errorf("fail to get spacex params from env: %w", err)
	}

	if params.Enabled && !params.MainHooker {
		// skip post-fetch hook if spacex is enabled but not the main hooker
		log.Info(ctx, "skip post-fetch hook intentionally")
		return 0, nil
	}

	repository, err := agitext.GetPbRepositoryFromEnv()
	if err != nil {
		return 1, fmt.Errorf("fail to extract pb.Repository from env: %w", err)
	}

	identityID := os.Getenv("GL_ID")
	log.Info(ctx, "begin to execute post-fetch hook",
		log.NewStringField("user", identityID),
		log.NewInt64Field("RepositoryID", repository.GlRepositoryId),
		log.NewStringField("Addrs", strings.Join(params.Addrs, ", ")),
	)

	// 2.Get post-fetch hook stream.
	conn, err := getSatelliteConn()
	if err != nil {
		return 1, fmt.Errorf("fail to create grpc client for post-fetch: %w", err)
	}
	defer conn.Close()

	ctx, cancel := context.WithTimeout(ctx, config.Cfg.PostFetchTimeout)
	defer cancel()
	postFetchHookStream, err := pb.NewHookServiceClient(conn).PostFetchHook(ctx)
	if err != nil {
		return 1, fmt.Errorf("fail to get post-fetch hook stream: %w", err)
	}

	// 3.Send request and stdin.
	if err := postFetchHookStream.Send(&pb.PostFetchHookRequest{
		Repository: repository,
		IdentityId: identityID,
		SyncAddrs:  params.Addrs,
		Action:     pb.PostFetchHookRequest_SYNC,
	}); err != nil {
		return 1, fmt.Errorf("fail to send post-fetch hook request: %w", err)
	}

	reqWriter := streamio.NewWriter(func(p []byte) error {
		return postFetchHookStream.Send(&pb.PostFetchHookRequest{Stdin: p})
	})

	_, errSend := io.Copy(reqWriter, os.Stdin)
	if errSend != nil {
		// This should not happen.
		errSend = fmt.Errorf("stdin send error: %v", errSend)
		return 1, errSend
	}

	// 4.Get response.
	_, err = postFetchHookStream.CloseAndRecv()
	if err != nil {
		return 1, err
	}
	return 0, nil
}

func fetchProxy(ctx context.Context, args []string) (int, error) {
	repository, err := agitext.GetPbRepositoryFromEnv()
	if err != nil {
		return 1, fmt.Errorf("fail to extract pb.Repository from env: %w", err)
	}

	conn, err := getSatelliteConn()
	if err != nil {
		return 1, fmt.Errorf("fail to create grpc client for post-fetch: %w", err)
	}
	defer conn.Close()

	ctx, cancel := context.WithTimeout(ctx, config.Cfg.PostFetchTimeout)
	defer cancel()

	postFetchHookStream, err := pb.NewHookServiceClient(conn).PostFetchHook(ctx)
	if err != nil {
		return 1, fmt.Errorf("fail to get post-fetch hook stream: %w", err)
	}

	if err = postFetchHookStream.Send(&pb.PostFetchHookRequest{
		Repository: repository,
		Action:     pb.PostFetchHookRequest_PROXY,
		Args:       args,
	}); err != nil {
		return 1, fmt.Errorf("fail to send post-fetch hook request: %w", err)
	}

	if _, err = postFetchHookStream.CloseAndRecv(); err != nil {
		return 1, fmt.Errorf("fail to wait post-fetch hook stream: %w", err)
	}

	return 0, nil
}

func getPostFetchHookEnv() pb.PostFetchHookRequest_Action {
	action := os.Getenv(_postFetchAction)
	switch action {
	case _proxy:
		return pb.PostFetchHookRequest_PROXY
	case _sync:
		fallthrough
	default:
		return pb.PostFetchHookRequest_SYNC
	}
}
